<?php

namespace app\system\admin;

use app\system\model\SystemModule as ModuleModel;
use one\Cloud;
use one\Dir;
use one\PclZip;
use think\Db;

/**
 * 在线升级控制器
 * @package app\system\admin
 */
class Upgrade extends Admin
{

    public $appType = 'system';
    public $identifier = 0;
    public $appVersion = '';
    public $baseConfig;
    protected function initialize()
    {
        parent::initialize();
        if (!isset($this->apiKey) || empty($this->apiKey) || cache('apiKey') != $this->apiKey) {
            exit(json_encode(['msg' => '非法操作！', 'code' => 710]));
        }

        $this->rootPath         = env('root_path');
        $this->appPath          = env('app_path');
        $this->updatePath       = $this->rootPath . 'backup/uppack/';
        $this->updateBackPath   = $this->rootPath . 'backup/upback/';
        $this->cloud            = new Cloud(config('one_cloud.key'), $this->updatePath);
        $this->appType          = $this->request->param('app_type/s', 'system');
        $this->identifier       = $this->request->param('identifier/s', 'system');
        $this->cacheUpgradeList = 'upgrade_version_list' . $this->identifier;
        $this->appKey           = '';

        $map = [];
        $map[] = ['identifier', '=', $this->identifier];
        $map[] = ['status', '<>', 0];
        switch ($this->appType) {
            case 'module':
                $this->appInfo      = (new ModuleModel)->where($map)->find();
                $this->appKey       = $this->appInfo->app_keys;
                $this->appVersion   = $this->appInfo->version;
                break;

            default:
                $this->appVersion = config('one.version');
                break;
        }

        if (!$this->appVersion) {
            return $this->error('未安装的插件或模块禁止更新');
        }
        $this->baseConfig = new \BaseConfig();
    }
    /**
     * 获取通讯状态
     *
     * @return void
     * @author 617 <email：723875993@qq.com>
     */
    public function getConnection()
    {
        $data = input();
        return $this->cloud->data($data)->api($this->baseConfig::$getConnection);
    }

    /**
     * 首页
     *
     * @return void
     * @author 617 <email：723875993@qq.com>
     */
    public function index()
    {
        if ($this->request->isPost()) {
            $account = $this->request->post('account/s');
            $password = $this->request->post('password/s');
            $type = $this->request->post('type/d');

            $data               = [];
            $data['account']    = $account;
            $data['password']   = $password;
            $data['type']       = $type;
            $data['site_name']  = config('base.site_name');

            switch ($data['type']) {
                case 1:
                    $res = $this->cloud->data($data)->api($this->baseConfig::$login);
                    break;
                case 2:
                    $repassword = $this->request->post('repassword/s');
                    $data['repassword']   = $repassword;
                    $res = $this->cloud->data($data)->api($this->baseConfig::$sign);
                    break;
                default:
                    return $this->error('云平台绑定失败！(-00)');
            }
            if (isset($res['code']) && $res['code'] == 0) {
                // 缓存站点标识
                $file = env('config_path') . 'one_cloud.php';
                $str = "<?php\n// 请妥善保管此文件，谨防泄漏\nreturn ['identifier' => '" . $res['data']['identifier'] . "'];\n";
                if (file_exists($file)) {
                    unlink($file);
                }
                $fp = fopen($file, "w+");
                fwrite($fp, $str);
                fclose($fp);
                if (!file_exists($file)) {
                    return $this->error('config/one_cloud.php写入失败');
                }
                return $this->success('恭喜您，已成功绑定云平台');
            }
            return $this->error($res['msg'] ? $res['msg'] : '云平台绑定失败！(-0)');
        }
        return $this->fetch();
    }

    /**
     * 升级文件列表
     * @return mixed
     */
    public function lists()
    {
        if ($this->request->isPost()) {
            if (!config('one_cloud.identifier')) {
                return $this->error('请绑定云平台');
            }

            $result = $this->getVersion();
            return json($result);
        }
    }

    /**
     * 下载升级包
     * @return mixed
     */
    public function download($version = '')
    {
        if (!$this->request->isPost()) {
            return $this->error('参数传递错误');
        }
        if (empty($version)) {
            return $this->error('参数传递错误');
        }
        if (!is_dir($this->updatePath)) {
            Dir::create($this->updatePath, 0755);
        }
        $lock = $this->updatePath . $this->identifier . 'upgrade.lock';
        // 暂时停用升级锁定
        if (!is_file($lock)) {
            file_put_contents($lock, time());
        } else {
            return $this->error("升级任务执行中，请手动删除此文件后重试！\n\r文件地址：/backup/uppack/" . $this->identifier . 'upgrade.lock');
        }
        $versions = $this->getVersion();
        // 检查当前升级补丁前面是否还有未升级的补丁
        $tobe = [];
        $file = '';
        foreach ($versions['data'] as $k => $v) {
            if (version_compare($v['version'], $version, '>=')) {
                if (version_compare($v['version'], $version, '=')) {
                    $file = $this->cloud->data([
                        'version' => $v['version'],
                        'app_identifier' => $this->identifier,
                        'app_key' => $this->appKey,
                        'app_type' => $this->appType,
                    ])->down($this->baseConfig::$download);
                    if ($file === false) {
                        $this->clearCache($file);
                        return $this->error('前置版本 ' . $v['version'] . ' 升级失败');
                    } else {
                        if ($this->_install($file, $v['version'], $this->appType) === false) {
                            $this->clearCache($file);
                            return $this->error($this->error);
                        }
                    }
                }
                break;
            } else {
                $file = $this->cloud->data([
                    'version' => $v['version'],
                    'app_identifier' => $this->identifier,
                    'app_key' => $this->appKey,
                    'app_type' => $this->appType,
                ])->down($this->baseConfig::$download);
                if ($file === false) {
                    $this->clearCache($file);
                    return $this->error('前置版本 ' . $v['version'] . ' 升级失败');
                } else {
                    if ($this->_install($file, $v['version'], $this->appType) === false) {
                        $this->clearCache($file);
                        return $this->error($this->error);
                    }
                }
            }
        }

        if ($file === false || empty($file)) {
            $this->clearCache($file);
            return $this->error('获取升级包失败');
        }
        return $this->success(basename($file));
    }

    /**
     * 安装方法
     * @return mixed
     */
    public function install($file = '', $version = '')
    {
        if (!$this->request->isPost()) {
            return $this->error('参数传递错误');
        }
        $file = $this->updatePath . $file;
        if (!file_exists($file)) {
            $this->clearCache($file);
            return $this->error($version . ' 升级包异常，请重新升级');
        }

        if ($this->_install($file, $version, $this->appType) === false) {
            $this->clearCache($file);
            return $this->error($this->error);
        }
        $jumpUrl = '';
        if ($this->appType == 'theme') {
            $param                  = $this->request->param('');
            $param['app_version']   = $param['version'];
            $param['app_name']      = cookie('upgrade_app_name');
            unset($param['file'], $param['version']);
            $jumpUrl = url('lists?' . http_build_query($param));
        }
        return $this->success('升级包安装成功', $jumpUrl);
    }

    /**
     * 执行安装
     * @return bool
     */
    private function _install($file = '', $version = '', $app_type = 'system')
    {
        if (empty($file) || empty($version)) {
            $this->error = '参数传递错误';
            return false;
        }
        switch ($app_type) {
            case 'module': // 模块升级安装
                return $this->_moduleInstall($file, $version);
                break;
            default: // 系统升级安装
                return $this->_systemInstall($file, $version);
                break;
        }
        clearstatcache();
    }

    /**
     * 系统升级
     * @return bool
     */
    private function _systemInstall($file, $version)
    {
        $_version = cache($this->cacheUpgradeList);
        $_version = $_version['data'];
        $md5file = md5_file($file);
        foreach ($_version as $value) {
            if ($version == $value['version']) {
                if ($md5file != $value['md5']) {
                    Dir::delDir($this->updatePath);
                    $this->error = '文件不完整，请重试升级！';
                    return false;
                }
            }
        }

        // 创建升级临时目录
        if (!is_dir($this->updateBackPath)) {
            Dir::create($this->updateBackPath);
        }

        // 创建解压缩文件目录
        $decomPath = $this->updatePath . basename($file, ".zip");
        if (!is_dir($decomPath)) {
            Dir::create($decomPath, 0777);
        }

        // 解压升级包，获取升级文件
        $archive = new PclZip();
        $archive->PclZip($file);
        if (!$archive->extract(PCLZIP_OPT_PATH, $decomPath, PCLZIP_OPT_REPLACE_NEWER)) {
            $this->error = '升级失败，请开启[/backup/uppack]文件夹权限';
            return false;
        }

        // ***********************************
        $importtxt = @implode('', file($decomPath . '/upgrade.xml'));
        $xml_info = xml2array($importtxt);
        $ADD = isset($xml_info['one_files']['A']) ? (array)$xml_info['one_files']['A'] : [];
        $UPDATE = isset($xml_info['one_files']['U']) ? (array)$xml_info['one_files']['U'] : [];
        $DELETE = isset($xml_info['one_files']['D']) ? (array)$xml_info['one_files']['D'] : [];
        $back_file = array_merge_recursive($ADD, $UPDATE, $DELETE);
        $back_file = array_unique($back_file);
        $back_dir = $this->updateBackPath . $version . '/';
        if (!is_dir($back_dir)) Dir::create($back_dir, 0777);

        //备份文件
        foreach ($back_file as $v) {
            $v  = trim($v, '/');
            $dir = $back_dir . dirname($v) . '/';
            if (!is_dir($dir)) {
                Dir::create($dir, 0777);
            }
            if (is_file($this->rootPath . $v)) {
                @copy($this->rootPath . $v, $dir . basename($v));
            }
        }

        //删除文件
        if (!empty($DELETE)) {
            foreach ($DELETE as $v) {
                @unlink($v);
            }
        }

        // 直接解压到根目录更新文件，然后更新sql
        if ($archive->extract(PCLZIP_OPT_PATH, $this->rootPath, PCLZIP_OPT_REPLACE_NEWER) == 0) {
            $this->error = '文件不存在,升级失败';
            return false;
        } else {
            // 导入SQL
            $sqlFile = realpath($decomPath . '/update.sql');
            if (is_file($sqlFile)) {
                $sql = file_get_contents($sqlFile);
                $sqlList = parse_sql($sql, 0, ['one_' => config('database.prefix')]);
                if ($sqlList) {
                    $sqlList = array_filter($sqlList);
                    foreach ($sqlList as $v) {
                        try {
                            Db::execute($v);
                        } catch (\Exception $e) {
                            $this->error = 'SQL更新失败';
                            return false;
                        }
                    }
                }
            }
        }
        $branch = config('one.branch');
        // 写版本信息
        $versiondata = <<<EOF
<?php
return [
    'one' =>
    [
        'name' => 'ONE-PHP',
        'version' => '{$version}',
        'branch' => '{$branch}',
        'release' => 0,
        'copyright' => '云影评',
        'url' => 'http://www.617kan.cn/',
    ],
];
EOF;
        file_put_contents($this->rootPath . 'version.php', $versiondata);
        @unlink($this->rootPath . 'update.sql');
        @unlink($this->rootPath . 'upgrade.xml');
        $this->clearCache('', $version);
        return true;
    }

    /**
     * 模块升级
     * @return bool
     */
    private function _moduleInstall($file, $version)
    {
        $backPath   = $this->updateBackPath . 'module/' . $this->appInfo['name'] . '/' . $this->appInfo['version'];
        $_version   = cache($this->cacheUpgradeList);
        $_version   = $_version['data'];
        $md5file    = md5_file($file);
        // if($md5file != $_version[$version]['md5']) {
        //     Dir::delDir($this->updatePath);
        //     $this->error = '文件不完整，请重新升级！';
        //     return false;
        // }
        if (!is_dir($backPath)) {
            Dir::create($backPath);
        }

        $decomPath = $this->updatePath . basename($file, ".zip");
        if (!is_dir($decomPath)) {
            Dir::create($decomPath, 0777);
        }

        // 解压升级包
        $archive = new PclZip();
        $archive->PclZip($file);
        if (!$archive->extract(PCLZIP_OPT_PATH, $decomPath, PCLZIP_OPT_REPLACE_NEWER)) {
            $this->error = '升级失败，请开启[/backup/uppack]文件夹权限';
            return false;
        }
        // 获取本次升级信息
        if (!is_file($decomPath . '/upgrade.php')) {
            $this->error = '升级失败，升级包文件不完整';
            return false;
        }

        $upInfo = include_once $decomPath . '/upgrade.php';
        //备份需要升级的旧版本
        if (isset($upInfo['update'])) {
            foreach ($upInfo['update'] as $k => $v) {
                $v  = trim($v, '/');
                $dir = $backPath . dirname($v) . '/';
                if (!is_dir($dir)) {
                    Dir::create($dir, 0777);
                }
                if (is_file($this->rootPath . $v)) {
                    @copy($this->rootPath . $v, $dir . basename($v));
                }
            }
        }

        // 根据升级补丁删除文件
        if (isset($upInfo['delete'])) {
            foreach ($upInfo['delete'] as $k => $v) {
                $v = trim($v, '/');
                // 锁定删除文件范围
                if ((substr($v, 0, strlen('application/' . $this->appInfo['name'])) == 'application/' . $this->appInfo['name'] ||
                    substr($v, 0, strlen('public/theme/' . $this->appInfo['name'])) == 'public/theme/' . $this->appInfo['name'] ||
                    substr($v, 0, strlen('public/static/' . $this->appInfo['name'])) == 'public/static/' . $this->appInfo['name']) && strpos($v, '..') === false) {
                    if (is_file($this->rootPath . $v)) {
                        @unlink($this->rootPath . $v);
                    }
                }
            }
        }

        //根据升级文件清单升级
        foreach ($upInfo['update'] as $k => $v) {
            $v = trim($v, '/');
            $dir = $this->rootPath . dirname($v) . '/';
            if (!is_dir($dir)) {
                Dir::create($dir, 0777);
            }

            if (is_file($decomPath . '/upload/' . $v)) {
                @copy($decomPath . '/upload/' . $v, $dir . basename($v));
            }
        }

        // 读取模块info
        if (!is_file($this->appPath . $this->appInfo['name'] . '/info.php')) {
            $this->error = $this->appInfo['name'] . '模块配置文件[info.php]丢失';
            return false;
        }

        $moduleInfo = include_once $this->appPath . $this->appInfo['name'] . '/info.php';
        if (!isset($moduleInfo['db_prefix']) || empty($moduleInfo['db_prefix'])) {
            $moduleInfo['db_prefix'] = 'db_';
        }

        // 整合模块配置
        $oldConfig = $newConfig = '';
        if (!empty($this->appInfo['config'])) {
            $oldConfig = json_decode($this->appInfo['config'], 1);
            sort($oldConfig);
            $oldColumn = array_column($oldConfig, 'name');
        }

        if (!empty($newConfig = $moduleInfo['config'])) {
            if (!empty($oldConfig)) {
                foreach ($newConfig as $k => &$v) {
                    $schKey = array_search($v['name'], $oldColumn);
                    if ($schKey !== false) {
                        $v['value'] = $oldConfig[$schKey]['value'];
                    }
                }
            }
            $newConfig = json_encode($newConfig, 1);
        }

        // 导入SQL
        $sqlFile = realpath($decomPath . '/update.sql');
        if (is_file($sqlFile)) {
            $sql = file_get_contents($sqlFile);
            $sqlList = parse_sql($sql, 0, [$moduleInfo['db_prefix'] => config('database.prefix')]);
            if ($sqlList) {
                $sqlList = array_filter($sqlList);
                foreach ($sqlList as $v) {
                    try {
                        Db::execute($v);
                    } catch (\Exception $e) {
                        $this->error = 'SQL更新失败';
                        return false;
                    }
                }
            }
        }

        // 更新模块信息
        $this->appInfo->version = $version;
        $this->appInfo->config = $newConfig;
        $this->appInfo->save();
        $this->clearCache('', $version);
        ModuleModel::getConfig('', true);

        return true;
    }

    /**
     * 获取升级版本
     * @return array
     */
    private function getVersion()
    {
        $cache = cache($this->cacheUpgradeList);
        if (isset($cache['data']) && !empty($cache['data'])) {
            //return $cache;
        }
        $result = $this->cloud->data([
            'app_identifier' => $this->identifier,
            'app_key' => $this->appKey
        ])->api($this->baseConfig::$versions);
        if ($result['code'] == 0) {
            cache($this->cacheUpgradeList, $result, 3600);
        }
        return $result;
    }

    /**
     * 清理升级包、升级锁、升级版本列表、升级解压文件
     * @param string $file 升级包文件路径
     * @param string $version 当前升级版本号
     */
    private function clearCache($file = '', $version = '')
    {
        if (is_file($this->updatePath . $this->identifier . 'upgrade.lock')) {
            unlink($this->updatePath . $this->identifier . 'upgrade.lock');
        }

        if (is_file($file)) {
            unlink($file);
        }

        // 在升级缓存列表里面清除已升级的版本信息
        if ($version) {
            $versionCache = cache($this->cacheUpgradeList);
            unset($versionCache['data'][$version]);
            cache($this->cacheUpgradeList, $versionCache, 3600);
        }

        // 删除升级解压文件
        if (is_dir($this->updatePath)) {
            Dir::delDir($this->updatePath);
        }

        if (!cache($this->cacheUpgradeList)) {
            // 删除系统缓存
            Dir::delDir(env('runtime_path') . 'cache');
            Dir::delDir(env('runtime_path') . 'temp');
        }
    }
}
